﻿
namespace Apache.Cassandra.Client {
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Data.Common;
    using System.Diagnostics;
    using System.Globalization;
    using System.Reflection;

    public sealed class CassandraConnectionStringBuilder : DbConnectionStringBuilder {

        private static readonly string[] keywords;
        private static readonly IDictionary<string, string> synonyms = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        private static readonly IDictionary<string, object> defaults = new Dictionary<string, object>(StringComparer.Ordinal);
        private static readonly IDictionary<string, Type> types = new Dictionary<string, Type>(StringComparer.Ordinal);

        private readonly IDictionary<string, object> values = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

        static CassandraConnectionStringBuilder() {
            PropertyInfo[] properties = typeof(CassandraConnectionStringBuilder).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
            IList<string> keywords = new List<string>();

            foreach (PropertyInfo property in properties) {
                if (property.GetGetMethod().IsVirtual) {
                    continue;
                }

                Type type = property.PropertyType;
                string keyword = property.Name;
                object[] attributes = property.GetCustomAttributes(false);

                foreach (Attribute attribute in attributes) {
                    DisplayNameAttribute displayName = attribute as DisplayNameAttribute;
                    if (null != displayName) {
                        keyword = displayName.DisplayName;
                        break;
                    }
                }

                keywords.Add(keyword);
                CassandraConnectionStringBuilder.synonyms.Add(keyword, keyword);
                CassandraConnectionStringBuilder.types.Add(keyword, type);

                foreach (Attribute attribute in attributes) {
                    SynonymsAttribute synonyms = attribute as SynonymsAttribute;
                    if (null != synonyms) {
                        foreach (string synonym in synonyms.Synonyms) {
                            CassandraConnectionStringBuilder.synonyms.Add(synonym, keyword);
                        }
                        continue;
                    }

                    DefaultValueAttribute defaultValue = attribute as DefaultValueAttribute;
                    if (null != defaultValue) {
                        CassandraConnectionStringBuilder.defaults.Add(keyword, ChangeType(keyword, defaultValue.Value));
                    }
                }
            }
            CassandraConnectionStringBuilder.keywords = new string[keywords.Count];
            keywords.CopyTo(CassandraConnectionStringBuilder.keywords, 0);

            Debug.Assert(CassandraConnectionStringBuilder.keywords.Length == CassandraConnectionStringBuilder.defaults.Count);
        }

        public CassandraConnectionStringBuilder() {
            Clear();
        }

        [DefaultValue("")]
        public string Server {
            get {
                return (string)GetValue("Server");
            }
            set {
                SetValue("Server", value);
            }
        }

        [DefaultValue(9160)]
        public int Port {
            get {
                return (int)GetValue("Port");
            }
            set {
                SetValue("Port", value);
            }
        }

        [DisplayName("Initial Keyspace")]
        [DefaultValue("")]
        public string InitialKeyspace {
            get {
                return (string)GetValue("Initial Keyspace");
            }
            set {
                SetValue("Initial Keyspace", value);
            }
        }

        [DefaultValue(true)]
        public bool Pooling {
            get {
                return (bool)GetValue("Pooling");
            }
            set {
                SetValue("Pooling", value);
            }
        }

        [DisplayName("Use Deactivate Queue")]
        [DefaultValue(false)]
        public bool UseDeactivateQueue {
            get {
                return (bool)GetValue("Use Deactivate Queue");
            }
            set {
                SetValue("Use Deactivate Queue", value);
            }
        }

        [DisplayName("Performance Counters Instance")]
        [DefaultValue("")]
        public string PerformanceCountersInstance {
            get {
                return (string)GetValue("Performance Counters Instance");
            }
            set {
                SetValue("Performance Counters Instance", value);
            }
        }

        public override object this[string keyword] {
            get {
                string key = GetKey(keyword);
                return GetValue(key);
            }
            set {
                if (null == value) {
                    Remove(keyword);
                } else {
                    string key = GetKey(keyword);
                    SetValue(key, value);
                }
            }
        }

        public override ICollection Keys { 
            get {
                return new ReadOnlyCollection<string>(keywords); 
            } 
        }
 
        public override ICollection Values {
            get {
                object[] values = new object[keywords.Length];
                for (int i = 0; i < values.Length; ++i) {
                    values[i] = GetValue(keywords[i]);
                }
                return new ReadOnlyCollection<object>(values);
            }
        }

        public override bool ContainsKey(string keyword) {
            if (null == keyword) {
                throw new ArgumentNullException("keyword");
            }

            return synonyms.ContainsKey(keyword); 
        }

        public override void Clear() {
            base.Clear();
            for (int i = 0; i < keywords.Length; ++i) {
                Reset(keywords[i]);
            }
        }

        private object GetValue(string key) {
            return values[key];
        }

        private string GetKey(string keyword) {
            if (null == keyword) {
                throw new ArgumentNullException("keyword");
            }

            string key;
            if (!synonyms.TryGetValue(keyword, out key)) {
                // TODO: Keyword not supported.
                throw new ArgumentException();
            }

            return key;
        }

        public override bool Remove(string keyword) {
            if (null == keyword) {
                throw new ArgumentNullException("keyword");
            }

            string key;
            if (!synonyms.TryGetValue(keyword, out key)) {
                return false;
            }

            if (!base.Remove(key)) {
                return false;
            }

            Reset(key);
            return true;
        }

        private void Reset(string key) {
            values[key] = defaults[key];
        }

        private void SetValue(string key, object value) {
            values[key] = ChangeType(key, value);
            base[key] = value;
        }

        public override bool TryGetValue(string keyword, out object value) {
            string key;
            if (!synonyms.TryGetValue(keyword, out key)) {
                value = null;
                return false;
            }
            value = GetValue(key);
            return true;
        }

        private static object ChangeType(string key, object value) {
            Type type = types[key];
            string s = value as string;

            if (type.IsEnum && null != s) {
                return Enum.Parse(type, s, true);
            } else {
                return Convert.ChangeType(value, type, CultureInfo.InvariantCulture);
            }
        }

        [AttributeUsage(AttributeTargets.Property)]
        private class SynonymsAttribute : Attribute {

            private string[] synonyms;

            public SynonymsAttribute(params string[] synonyms) {
                this.synonyms = synonyms;
            }

            public string[] Synonyms {
                get {
                    return this.synonyms;
                }
            }
        }
    }
}
